Signals are a form of intra-program interrupt which can greatly aid clean, inexpensive error trapping in stack frame intensive languages. A program may invoke the Signal procedure and immediately return to the last invocation of CatchSignal, including the complete stack frame state at that point.
Signals allow a program to leave off execution at one point and return control to a convenient error trap location, regardless of how many levels of procedure nesting are in between.
The example is provided with a Pascal interface, but it is easily adapted to other languages. The only qualification is that the language must bracket its procedures (or functions) with LINK and UNLK instructions. This will allow the signal code to clean up at procedure exit time by removing CatchSignal entries from its internal queue.
Note: only procedures and/or functions that call CatchSignal need to be bracketed with LINK and UNLK instructions.
Important: InitSignals must be called from the main program so that A6 can be set up properly.
Note that there is no limit to the number of local CatchSignals which may occur within a single routine. Only the last one executed will apply, of course, unless you call FreeSignal. FreeSignal will ΓÇ£popΓÇ¥ off the last CatchSignal. If you attempt to Signal with no CatchSignals pending, Signal will halt the program with a debugger trap.
InitSignals creates a small relocatable block in the application heap to hold the signal queue. If CatchSignal is unable to expand this block (which it does 5 elements at a time), then it will signal back to the last successful CatchSignal with code = 200. A Signal(0) acts as a NOP, so you may pass OSErrs, for instance, after making File System type calls, and, if the OSErr is equal to NoErr, nothing will happen.
CatchSignal may not be used in an expression if the stack is used to evaluate that expression. For example, you canΓÇÖt write:
c:= 3*CatchSignal;
ΓÇ£GotchaΓÇ¥ summary
1. Routines which call CatchSignal must have stack frames.
2. InitSignals must be called from the outermost (main) level.
3. DonΓÇÖt put the CatchSignal function in an expression. Assign the result to
an INTEGER variable; i.e. i:=CatchSignal.
4. ItΓÇÖs safest to call a procedure to do the processing after CatchSignal
returns. See the Pascal example TestSignals below. This will prevent the
use of a variable which may be held in a register.
Below are three separate source files. First is the Pascal interface to the signaling unit, then the assembly language which implements it in MPW Assembler format. Finally, there is an example program which demonstrates the use of the routines in the unit.
{File ErrSignal.p}
UNIT ErrSignal;
INTERFACE
{Call this right after your other initializations (InitGraf, etc.)--in other words as early as you can in the application}
PROCEDURE InitSignals;
{Until the procedure which encloses this call returns, it will catch subsequent Signal calls, returning the code passed to Signal. When CatchSignal is encountered initially, it returns a code of zero. These calls may "nest"; i.e. you may have multiple CatchSignals in one procedure.
Each nested CatchSignal call uses 12 bytes of heap space }
FUNCTION CatchSignal:INTEGER;
{This undoes the effect of the last CatchSignal. A Signal will then invoke the CatchSignal prior to the last one.}
PROCEDURE FreeSignal;
{Returns control to the point of the last CatchSignal. The program will then behave as though that CatchSignal had returned with the code parameter supplied to Signal.}
PROCEDURE Signal(code:INTEGER);
END.
{End of ErrSignal.p}
HereΓÇÖs the assembly source for the routines themselves:
; ErrSignal code w. InitSignal, CatchSignal,FreeSignal, Signal
; defined
;
; Version 1.0 by Rick Blair
PRINT OFF
INCLUDE 'Traps.a'
INCLUDE 'ToolEqu.a'
INCLUDE 'QuickEqu.a'
INCLUDE 'SysEqu.a'
PRINT ON
CatchSigErr EQU 200 ;"insufficient heap" message
SigChunks EQU 5 ;number of elements to expand by
FrameRet EQU 4 ;return addr. for frame (off A6)
SigBigA6 EQU $FFFFFFFF ;maximum positive A6 value
; A template in MPW Assembler describes the layout of a collection of data
; without actually allocating any memory space. A template definition starts ; with a RECORD directive and ends with an ENDR directive.
; To illustrate how the template type feature works, the following template
; is declared and used. By using this, the assembler source appromixates very ; closely Pascal source for referencing the corresponding information.
;template for our table elements
SigElement RECORD 0 ;the zero is the template origin
SigSP DS.L 1 ;the SP at the CatchSignalΓÇö(DS.L just like EQU)
SigRetAddr DS.L 1 ;the address where the CatchSignal returned
SigFRet DS.L 1 ;return addr. for encl. procedure
SigElSize EQU * ;just like EQU 12
ENDR
; The global data used by these routines follows. It is in the form of a
; RECORD, but, unlike above, no origin is specified, which means that memory
; space *will* be allocated.
; This data is referenced through a WITH statement at the beginning of the
; procs that need to get at this data. Since the Assembler knows when it is
; referencing data in a data module (since they must be declared before they
; are accessed), and since such data can only be accessed based on A5, there
; is no need to explicitly specify A5 in any code which references the data
; (unless indexing is used). Thus, in this program we have omitted all A5
; references when referencing the data.
SigGlobals RECORD ;no origin means this is a data record
;not a template(as above)
SigEnd DS.L 1 ;current end of table
SigNow DS.L 1 ;the MRU element
SigHandle DC.L 0 ;handle to the table
ENDR
InitSignals PROC EXPORT ;PROCEDURE InitSignals;
IMPORT CatchSignal
WITH SigElement,SigGlobals
;the above statement makes the template SigElement and the global data
;record SigGlobals available to this procedure
MOVE.L #SigChunks*SigElSize,D0
_NewHandle ;try to get a table
BNE.S forgetit ;we couldn't get that!?
MOVE.L A0,SigHandle ;save it
MOVE.L #-SigElSize,SigNow ;point "now" before start